#include "buffer_pool.h"
#include "file_io.h"

#include <stdio.h>
#include <stdlib.h>

void init_buffer_pool(const char* filename, BufferPool* pool) {
    short i = 0;

    // open file 
    if (open_file(&(pool->file), filename) != FILE_IO_SUCCESS) {
        printf("init BufferPool error: file cannot open\n");
        pool->file.fp = NULL;
        return;
    }
    // initialize BufferPool
    for (i = 0; i < CACHE_PAGE; i++) {
        pool->pages[i] = EMPTY_PAGE;
        pool->addrs[i] = -1;
        pool->age[i] = 1;
        pool->ref[i] = 0;
    }
}

void close_buffer_pool(BufferPool* pool) {
    if (pool->file.fp == NULL) {
        printf("close BufferPool error: file dose not exist\n");
        return;
    }

    short i = 0;

    // check if every page's ref = 0
    for (i = 0; i < CACHE_PAGE; i++)
        if (pool->ref[i] != 0) {
            printf("close BufferPool error: existing page in use\n");
            return;
        }
    // apply `write_page` to all used pages in BufferPool
    for (i = 0; i < CACHE_PAGE; i++)
        if (pool->addrs[i] != -1)
            write_page(&(pool->pages[i]), &(pool->file), pool->addrs[i]);
    // close file
    close_file(&(pool->file));
}

Page* get_page(BufferPool* pool, off_t addr) {
    short i = 0, j = 0, pos = -1;

    //try to find addr in the buffer pool
    for (i = 0; i < CACHE_PAGE; i++)
        if (pool->ref[i] > 0 && pool->addrs[i] == addr) { // in buffer pool & in use
            pos = i;
            pool->ref[pos]++;
            break;
        }
    //fetch from file
    if (pos == -1) { // not in use or not in buffer pool
        for (i = 0; i < CACHE_PAGE; i++) {
            if (pool->ref[i] == 0 && pool->addrs[i] == addr) { // in buffer pool but not in use
                pos = i;
                pool->ref[pos] = 1;
                // update age
                size_t old_age = pool->age[pos];
                pool->age[pos] = 0;
                for (j = 0; j < CACHE_PAGE; j++)
                    if (pool->ref[j] == 0 && pool->addrs[i] != -1 && old_age < pool->age[j] && j != pos)
                        pool->age[j]--;
                break;
            }
        }
        if (pos == -1) { // not in buffer pool
            size_t max = 0;
            int is_direct_fetch = 0;
            for (i = 0; i < CACHE_PAGE; i++) {
                if (pool->addrs[i] == -1) {
                    pos = i;
                    is_direct_fetch = 1;
                    break;
                }
                if (pool->ref[i] == 0 && max < pool->age[i]) { // not in buffer pool, must replace
                    max = pool->age[i];
                    pos = i;
                }
            }
            if (pos == -1) {
                printf("get page error: no more available space in the buffer pool to replace\n");
                return NULL;
            }
            // `write_page`
            if (pool->addrs[pos] != -1)
                write_page(&(pool->pages[pos]), &(pool->file), pool->addrs[pos]);
            // `read_page` and update BufferPool
            if (addr == pool->file.length) {  /* append new page in the end*/
                Page page = EMPTY_PAGE;
                write_page(&page, &(pool->file), addr);
            }
            read_page(&(pool->pages[pos]), &(pool->file), addr);
            pool->addrs[pos] = addr;
            pool->ref[pos] = 1;
            // update age
            size_t old_age = pool->age[pos];
            pool->age[pos] = 0;
            if (!is_direct_fetch)
                for (j = 0; j < CACHE_PAGE; j++)
                    if (pool->ref[j] == 0 && pool->addrs[i] != -1 && old_age < pool->age[j] && j != pos)
                        pool->age[j]--;
        }

    }
    // return page
    return &(pool->pages[pos]);
}

void release(BufferPool* pool, off_t addr) {
    short i = 0;
    // find the pos
    int pos = -1;
    for (i = 0; i < CACHE_PAGE; i++)
        if (pool->ref[i] > 0 && pool->addrs[i] == addr) {
            pos = i;
            break;
        }
    if (pos == -1) {
        printf("release page error: this page is not in the buffer pool or not in use\n");
        return;
    }
    pool->ref[pos]--;
    // update age
    if (pool->ref[pos] == 0) {
        size_t old_age = pool->age[pos];
        pool->age[pos] = 1;
        for (i = 0; i < CACHE_PAGE; i++)
            if (pool->ref[i] == 0 && pool->addrs[i] != -1 && i != pos)
                pool->age[i]++;
    }
}

/* void print_buffer_pool(BufferPool *pool) {
} */

/* void validate_buffer_pool(BufferPool *pool) {
} */
